package com.nulldev.util.internal.backport.httpclient_rw.impl.common;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLParameters;

import com.nulldev.util.VariableAPI.util.strings.splitters.third_party.Splitter;
import com.nulldev.util.internal.backport.httpclient_rw.HttpHeaders;
import com.nulldev.util.internal.backport.httpclient_rw.impl.frame.DataFrame;
import com.nulldev.util.internal.backport.httpclient_rw.impl.frame.Http2Frame;
import com.nulldev.util.internal.backport.httpclient_rw.impl.frame.WindowUpdateFrame;

public abstract class Log implements ILogger {
	static final String logProp = "jdk.httpclient.HttpClient.log";

	public static final int OFF = 0;
	public static final int ERRORS = 0x1;
	public static final int REQUESTS = 0x2;
	public static final int HEADERS = 0x4;
	public static final int CONTENT = 0x8;
	public static final int FRAMES = 0x10;
	public static final int SSL = 0x20;
	public static final int TRACE = 0x40;
	public static final int CHANNEL = 0x80;
	static int logging;

	// Frame types: "control", "data", "window", "all"
	public static final int CONTROL = 1; // all except DATA and WINDOW_UPDATES
	public static final int DATA = 2;
	public static final int WINDOW_UPDATES = 4;
	public static final int ALL = CONTROL | DATA | WINDOW_UPDATES;
	static int frametypes;

	static final ILogger logger;
	private static final Splitter COLON_SPLIT = Splitter.on(":");

	static {
		String s = Utils.getNetProperty(logProp);
		if (s == null) {
			logging = OFF;
		} else {
			String[] vals = Utils.COMMA_SPLIT.splitToArray(s);
			for (String val : vals) {
				switch (val.toLowerCase(Locale.US)) {
					case "errors":
						logging |= ERRORS;
						break;
					case "requests":
						logging |= REQUESTS;
						break;
					case "headers":
						logging |= HEADERS;
						break;
					case "content":
						logging |= CONTENT;
						break;
					case "ssl":
						logging |= SSL;
						break;
					case "channel":
						logging |= CHANNEL;
						break;
					case "trace":
						logging |= TRACE;
						break;
					case "all":
						logging |= CONTENT | HEADERS | REQUESTS | FRAMES | ERRORS | TRACE | SSL | CHANNEL;
						frametypes |= ALL;
						break;
					default:
						// ignore bad values
				}
				if (val.startsWith("frames")) {
					logging |= FRAMES;
					String[] types = COLON_SPLIT.splitToArray(val);
					if (types.length == 1) {
						frametypes = CONTROL | DATA | WINDOW_UPDATES;
					} else {
						for (String type : types) {
							switch (type.toLowerCase(Locale.US)) {
								case "control":
									frametypes |= CONTROL;
									break;
								case "data":
									frametypes |= DATA;
									break;
								case "window":
									frametypes |= WINDOW_UPDATES;
									break;
								case "all":
									frametypes = ALL;
									break;
								default:
									// ignore bad values
							}
						}
					}
				}
			}
		}
		if (logging != OFF) {
			logger = LoggerCompatLayer.getLogger("jdk.httpclient.HttpClient");
		} else {
			logger = null;
		}
	}

	public static boolean errors() {
		return (logging & ERRORS) != 0;
	}

	public static boolean requests() {
		return (logging & REQUESTS) != 0;
	}

	public static boolean headers() {
		return (logging & HEADERS) != 0;
	}

	public static boolean trace() {
		return (logging & TRACE) != 0;
	}

	public static boolean ssl() {
		return (logging & SSL) != 0;
	}

	public static boolean frames() {
		return (logging & FRAMES) != 0;
	}

	public static boolean channel() {
		return (logging & CHANNEL) != 0;
	}

	public static void logError(String s, Object... s1) {
		if (errors()) {
			logger.log(Level.INFO, "ERROR: " + s, s1);
		}
	}

	public static void logError(Throwable t) {
		if (errors()) {
			String s = Utils.stackTrace(t);
			logger.log(Level.INFO, "ERROR: " + s);
		}
	}

	public static void logSSL(String s, Object... s1) {
		if (ssl()) {
			logger.log(Level.INFO, "SSL: " + s, s1);
		}
	}

	public static void logSSL(Supplier<String> msgSupplier) {
		if (ssl()) {
			logger.log(Level.INFO, "SSL: " + msgSupplier.get());
		}
	}

	public static void logChannel(String s, Object... s1) {
		if (channel()) {
			logger.log(Level.INFO, "CHANNEL: " + s, s1);
		}
	}

	public static void logChannel(Supplier<String> msgSupplier) {
		if (channel()) {
			logger.log(Level.INFO, "CHANNEL: " + msgSupplier.get());
		}
	}

	public static void logTrace(String s, Object... s1) {
		if (trace()) {
			String format = "MISC: " + s;
			logger.log(Level.INFO, format, s1);
		}
	}

	public static void logRequest(String s, Object... s1) {
		if (requests()) {
			logger.log(Level.INFO, "REQUEST: " + s, s1);
		}
	}

	public static void logResponse(Supplier<String> supplier) {
		if (requests()) {
			logger.log(Level.INFO, "RESPONSE: " + supplier.get());
		}
	}

	public static void logHeaders(String s, Object... s1) {
		if (headers()) {
			logger.log(Level.INFO, "HEADERS: " + s, s1);
		}
	}

	public static boolean loggingFrame(Class<? extends Http2Frame> clazz) {
		if (frametypes == ALL) {
			return true;
		}
		if (clazz == DataFrame.class) {
			return (frametypes & DATA) != 0;
		} else if (clazz == WindowUpdateFrame.class) {
			return (frametypes & WINDOW_UPDATES) != 0;
		} else {
			return (frametypes & CONTROL) != 0;
		}
	}

	public static void logFrames(Http2Frame f, String direction) {
		if (frames() && loggingFrame(f.getClass())) {
			logger.log(Level.INFO, "FRAME: " + direction + ": " + f.toString());
		}
	}

	public static void logParams(SSLParameters p) {
		if (!Log.ssl()) {
			return;
		}

		if (p == null) {
			Log.logSSL("SSLParameters: Null params");
			return;
		}

		final StringBuilder sb = new StringBuilder("SSLParameters:");
		final List<Object> params = new ArrayList<>();
		if (p.getCipherSuites() != null) {
			for (String cipher : p.getCipherSuites()) {
				sb.append("\n    cipher: {").append(params.size()).append("}");
				params.add(cipher);
			}
		}

		// SSLParameters.getApplicationProtocols() can't return null
		// JDK 8 EXCL START
		for (String approto : p.getApplicationProtocols()) {
			sb.append("\n    application protocol: {").append(params.size()).append("}");
			params.add(approto);
		}
		// JDK 8 EXCL END

		if (p.getProtocols() != null) {
			for (String protocol : p.getProtocols()) {
				sb.append("\n    protocol: {").append(params.size()).append("}");
				params.add(protocol);
			}
		}

		if (p.getEndpointIdentificationAlgorithm() != null) {
			sb.append("\n    endpointIdAlg: {").append(params.size()).append("}");
			params.add(p.getEndpointIdentificationAlgorithm());
		}

		if (p.getServerNames() != null) {
			for (SNIServerName sname : p.getServerNames()) {
				sb.append("\n    server name: {").append(params.size()).append("}");
				params.add(sname.toString());
			}
		}
		sb.append('\n');

		Log.logSSL(sb.toString(), params.toArray());
	}

	public static void dumpHeaders(StringBuilder sb, String prefix, HttpHeaders headers) {
		if (headers != null) {
			Map<String, List<String>> h = headers.map();
			Set<Map.Entry<String, List<String>>> entries = h.entrySet();
			String sep = "";
			for (Map.Entry<String, List<String>> entry : entries) {
				String key = entry.getKey();
				List<String> values = entry.getValue();
				if (values == null || values.isEmpty()) {
					// should not happen
					sb.append(sep);
					sb.append(prefix).append(key).append(':');
					sep = "\n";
					continue;
				}
				for (String value : values) {
					sb.append(sep);
					sb.append(prefix).append(key).append(':');
					sb.append(' ').append(value);
					sep = "\n";
				}
			}
			sb.append('\n');
		}
	}

	// not instantiable
	private Log() {
	}
}
